﻿// //  ********************************************************************************
// //  Copyright (C) 2016 Sebastian Kenter. All rights reserved.
// // 
// //  Name    : PhotonServerProject | PhotonServerFramework | ClientPeer.cs
// //  Created : 2016 - 03 - 21 11:23
// //  Modified: 2016 - 04 - 18 09:37
// //  ********************************************************************************

using System;
using System.Collections.Generic;
using System.Net;
using ExitGames.Logging;
using NHibernate;
using Photon.SocketServer;
using PhotonHostRuntimeInterfaces;
using PhotonServerCommon.Entities.Domain;
using PhotonServerCommon.Enums;
using PhotonServerFramework.Implementation.Configuration;
using PhotonServerFramework.Implementation.Database;
using PhotonServerFramework.Implementation.Telegram;
using PhotonServerFramework.Interfaces.Client;
using PhotonServerFramework.Interfaces.Configuration;
using PhotonServerFramework.Interfaces.Handler;

namespace PhotonServerFramework.Implementation.Client
{
    public class ClientPeer : PeerBase, IClientPeer
    {
        public ILogger Logger { get; }
        public Guid ClientPeerId { get; } = Guid.NewGuid();
        public IPeerData ClientPeerData { get; }

        private readonly IClientHandlerRegistry _clientHandlerRegistry;

        public ClientPeer(InitRequest initRequest, ILogger logger, IClientHandlerRegistry clientHandlerRegistry)
            : base(initRequest)
        {
            Logger = logger;
            _clientHandlerRegistry = clientHandlerRegistry;

            ClientPeerData = new PeerData
                             {
                                 ApplicationId = initRequest.ApplicationId,
                                 ConnectionId = initRequest.ConnectionId,
                                 PeerType = initRequest.PhotonPeer.GetPeerType().ToString(),
                                 ClientVersion = initRequest.ClientVersion.ToString(),
                                 LocalEndPoint =
                                     new IPEndPoint(IPAddress.Parse(initRequest.LocalIP),
                                     initRequest.LocalPort),
                                 RemoteEndPoint =
                                     new IPEndPoint(IPAddress.Parse(initRequest.RemoteIP),
                                     initRequest.RemotePort),
                                 NetworkProtocol = NetworkProtocol.ToString(),
                                 PeerId = ClientPeerId,
                                 PhotonProtocol = Protocol.ProtocolType.ToString()
                             };
        }

        public ClientPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer, ILogger logger)
            : base(protocol, unmanagedPeer)
        {
            Logger = logger;
        }


        public void Connect()
        {
        }

        public void Disconnect(string server)
        {
            lock (this)
            {
                using (ISession session = SessionManager.OpenSession())
                {
                    using (ITransaction transaction = session.BeginTransaction())
                    {
                        try
                        {
                            ClientPeerT cpeer =
                                session.QueryOver<ClientPeerT>()
                                    .Where(c => c.PeerId == ClientPeerId.ToString())
                                    .SingleOrDefault();
                            if (cpeer != null)
                            {
                                session.Lock(cpeer, LockMode.Upgrade);
                                cpeer.Connected = 0;
                                cpeer.Tsn += 1;
                                session.Update(cpeer);
                            }
                            transaction.Commit();
                        }
                        catch (Exception ex)
                        {
                            Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                            transaction.Rollback();
                        }
                    }

                    using (ITransaction transaction = session.BeginTransaction())
                    {
                        try
                        {
                            ClientPeerT cpeer =
                                session.QueryOver<ClientPeerT>()
                                    .Where(c => c.PeerId == ClientPeerId.ToString())
                                    .SingleOrDefault();
                            if (cpeer != null)
                            {
                                session.Lock(cpeer, LockMode.Upgrade);
                                cpeer.Tsn += 1;
                                session.Delete(cpeer);
                            }
                            session.Lock(cpeer, LockMode.Upgrade);

                            transaction.Commit();
                        }
                        catch (Exception ex)
                        {
                            Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                            transaction.Rollback();
                        }
                    }

                    session.Close();
                }
                Logger.InfoFormat("Disconnected client peer with Guid = {0} requested by server {1}",
                    ClientPeerId.ToString(), server);
            }
        }

        protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
        {
            Logger.InfoFormat(
                "Telegram received with parameters: type={0}, code={1}, subcode={2}",
                TelegramTypes.Request, (OperationCodes) operationRequest.OperationCode,
                (OperationSubCodes) operationRequest.Parameters[(byte) ParameterCodes.OperationSubCode]);

            HandlerT handler = null;
            ClientPeerT clientPeer = null;

            using (ISession session = SessionManager.OpenSession())
            {
                // Fetch the client peer entry from db
                using (ITransaction transaction = session.BeginTransaction())
                {
                    Logger.InfoFormat("Fetching client peer entry from database for client peer id {0}", ClientPeerId);
                    try
                    {
                        clientPeer =
                            session.QueryOver<ClientPeerT>()
                                .Where(c => c.PeerId == ClientPeerId.ToString())
                                .SingleOrDefault();
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                        transaction.Rollback();
                    }
                }

                if (clientPeer != null)
                {
                    Logger.InfoFormat("Entry found in database - processing telegram");

                    // Trying to find a client handler on this server to handle the telegram
                    using (ITransaction transaction = session.BeginTransaction())
                    {
                        Logger.InfoFormat(
                            "Trying to resolve client handler from db with parameters: type={0}, code={1}, subcode={2}",
                            HandlerTypes.Request, (OperationCodes) operationRequest.OperationCode,
                            (OperationSubCodes) operationRequest.Parameters[(byte) ParameterCodes.OperationSubCode]);

                        try
                        {
                            handler =
                                session.QueryOver<HandlerT>()
                                    .Where(h => h.HandlerType == (int) HandlerTypes.Request)
                                    .And(h => h.HandlerCode == operationRequest.OperationCode)
                                    .And(
                                        h =>
                                            h.HandlerSubCode == (int)
                                                operationRequest.Parameters[(byte) ParameterCodes.OperationSubCode])
                                    .And(h => h.ClientHandler == Convert.ToInt32(true))
                                    .SingleOrDefault();

                            transaction.Commit();
                        }
                        catch (Exception ex)
                        {
                            Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                            transaction.Rollback();
                        }
                    }

                    // No local handler found, searching forwarding handler on this server
                    if (handler == null)
                    {
                        Logger.InfoFormat("No handler found on this server - trying to forward telegram");

                        using (ITransaction transaction = session.BeginTransaction())
                        {
                            Logger.InfoFormat("Fetching handler for forwarding request from database");
                            try
                            {
                                handler =
                                    session.QueryOver<HandlerT>()
                                        .Where(c => c.HandlerType == (int) TelegramTypes.Request)
                                        .And(c => c.HandlerCode == (int) OperationCodes.ForwardRequest)
                                        .And(c => c.ClientHandler == 1)
                                        .And(c => c.ServerId == clientPeer.ConnectedServerId)
                                        .SingleOrDefault();
                                transaction.Commit();
                            }
                            catch (Exception ex)
                            {
                                Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                                transaction.Rollback();
                            }
                        }
                    }

                    // Found forwarding handler on this server => forwarding directly
                    if (handler != null && handler.HandlerCode == (int) OperationCodes.ForwardRequest)
                    {
                        Logger.InfoFormat("Forwarding handler found with id={0} => forwarding telegram",
                            handler.HandlerId);

                        Dictionary<byte, object> parameters = operationRequest.Parameters;
                        parameters.Add((byte) ParameterCodes.ClientPeerId, ClientPeerId.ToString());
                        parameters.Add((byte) ParameterCodes.ServerId, handler.ServerId);

                        _clientHandlerRegistry.GetClientHandlerById(Guid.Parse(handler.HandlerId))
                            .HandleTelegram(
                                new RequestTelegram
                                {
                                    TelegramCode = operationRequest.OperationCode,
                                    TelegramSubCode =
                                        (int)
                                        operationRequest.Parameters[
                                            (byte) ParameterCodes.OperationSubCode],
                                    TelegramParameters = operationRequest.Parameters
                                }, this);
                    }
                    // Found handler on this server capable of handling the telegram => handling directly
                    else if (handler != null && handler.HandlerCode != (int) OperationCodes.ForwardRequest)
                    {
                        Logger.InfoFormat("Found handler on this server => handling telegram");
                        _clientHandlerRegistry.GetClientHandlerById(Guid.Parse(handler.HandlerId))
                            .HandleTelegram(
                                new RequestTelegram
                                {
                                    TelegramCode = operationRequest.OperationCode,
                                    TelegramSubCode =
                                        (int)
                                        operationRequest.Parameters[
                                            (byte) ParameterCodes.OperationSubCode],
                                    TelegramParameters = operationRequest.Parameters
                                }, this);
                    }
                    else
                    {
                        Logger.ErrorFormat("No handler found at all.");
                    }

                    session.Close();
                }
                else
                {
                    Logger.ErrorFormat("Client peer not registered at master server!");
                }
            }
        }

        protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
        {
            Logger.InfoFormat("Disconnecting client {0} form server {0} (Guid: {1})", ClientPeerId,
                ClientPeerData.ApplicationId,
                ClientPeerId);
            Disconnect(ClientPeerData.ApplicationId);
        }
    }
}
